package com.hy.formula.runtime;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.hy.formula.common.CollectionUtils;
import com.hy.formula.common.Symbol;
import com.hy.formula.entity.Token;
import com.hy.formula.exception.FormulaException;
import com.hy.formula.function.ConcatFunc;
import com.hy.formula.function.IFunction;
import com.hy.formula.function.RandFunc;
import com.hy.formula.function.SubFunc;
import com.hy.formula.function.SumFunc;
import com.hy.formula.scanner.Scanner;

/**
 * 公式解析器
 * @author huangy
 * @date 2015年4月29日 下午3:38:26
 */
public class FRuntime {

	private Map<String,IFunction> funcMap;
	
	public FRuntime(){
		funcMap = new HashMap<String, IFunction>();
		
		funcMap.put("sum", new SumFunc());
		funcMap.put("rand", new RandFunc());
		funcMap.put("concat", new ConcatFunc());
		funcMap.put("sub", new SubFunc());
	}
	
	public Map<String,IFunction> getFuncMap(){
		return funcMap;
	}
	
	/**
	 * 解析tonken流，并执行获得结果
	 * 基于栈的解析执行
	 * @param tokenList
	 * @return
	 */
	public Object execute(List<Token> tokenList){
		FRuntimeContext context = new FRuntimeContext();
		if(!CollectionUtils.isEmpty(tokenList)){
			for(Token token : tokenList){
				switch(token.getSymbol()){
				case FUNC:
				case LEFT_PARENTHESIS:
				case COMMA:
					context.putOp(token);
					break;
				case RIGHT_PARENTHESIS:
					List<Token> paramsList = new ArrayList<Token>();
					paramsList.add(context.popValue());
					while(!Symbol.LEFT_PARENTHESIS.equals(context.getOp().getSymbol())){
						//一直弹出操作符，这里应该是逗号，直到遇到左括号
						context.popOp();
						paramsList.add(context.popValue());
					}
					//弹出左括号
					context.popOp();
					//弹出函数信息
					Token funcToken = context.popOp();
					//设置当前函数信息
					context.setCurrentFunc(funcToken);
					//参数逆序排列
					Collections.reverse(paramsList);
					//调用函数，获得执行结果
					Object formulaResult = execute(funcToken,context,paramsList);
					//结果压入值栈
					context.putValue(createToken(formulaResult,true));
					break;
				case NUMBER:
				case STRING:
				default:
					context.putValue(token);
					break;
				}
			}
		}
		if(context.getOpStack().isEmpty() 
				&& context.getValue() != null
				&& context.getValStack().size() == 1){
			//只有值栈有一个数据的才是结果
			return translateValue(context.popValue());
		}else{
			throw new RuntimeException("执行出错，请检查公式。错误原因：符号栈里还有操作符或是值栈里有多个值");
		}
	}

	/**
	 * 
	 * @param formulaResult
	 * @param isVal
	 * @return
	 */
	private Token createToken(Object formulaResult, boolean isVal) {
		Token token = new Token();
		if(formulaResult != null){
			token.setValue(formulaResult.toString());
		}
		Symbol symbol = Symbol.VOID;
		if(formulaResult != null){
			if(formulaResult instanceof Number){
				symbol = Symbol.NUMBER;
			}else{
				symbol = Symbol.STRING;
			}
		}
		token.setSymbol(symbol);
		return token;
	}

	/**
	 * 执行函数
	 * @param funcToken
	 * @param context
	 * @param paramsList
	 * @return
	 */
	private Object execute(Token funcToken, FRuntimeContext context,
			List<Token> paramsList) {
		if(this.getFuncMap().containsKey(funcToken.getValue())){
			IFunction func = this.getFuncMap().get(funcToken.getValue());
			Object[] params = new Object[paramsList.size()];
			for(int i = 0; i < paramsList.size(); i++){
				params[i] = translateValue(paramsList.get(i));
			}
			return func.execute(context, params);
		}else{
			throw new FormulaException("没有此公式:"+funcToken.getValue());
		}
	}

	/**
	 * 把符号流里的值翻译成正确的java类型
	 * @param valToken
	 * @return
	 */
	private Object translateValue(Token valToken) {
		if(Symbol.STRING.equals(valToken.getSymbol())){
			return valToken.getValue();
		}else if(Symbol.NUMBER.equals(valToken.getSymbol())){
			if(valToken.getValue().indexOf("-") > 0){
				throw new NumberFormatException("负数格式不对。"+valToken.getValue());
			}
			//有小数点的翻译成float类型
			if(valToken.getValue().indexOf(".") > 0){
				return Float.parseFloat(valToken.getValue());
			}else{
				return Integer.parseInt(valToken.getValue());
			}
		}
		return valToken.getValue();
	}
	
	/**
	 * 解析执行公式
	 * @param formula
	 * @return
	 */
	public Object parse(String formula){
		Scanner scanner = new Scanner();
		List<Token> tokenList = scanner.getTokens(formula);
		return this.execute(tokenList);
	}
	
}
